#!/usr/bin/env groovy

pipeline {
  agent none

  parameters {
    choice(
      name: 'PX4_CMAKE_BUILD_TYPE',
      choices: ['RelWithDebInfo', 'Coverage', 'AddressSanitizer', 'UndefinedBehaviorSanitizer'],
      description: "CMake build type"
      )
  }

  stages {

    stage('Build') {

      agent {
        docker {
          image 'px4io/px4-dev-ros-kinetic:2018-09-11'
          args '-e CCACHE_BASEDIR=$WORKSPACE -v ${CCACHE_DIR}:${CCACHE_DIR}:rw -e HOME=$WORKSPACE --cap-add SYS_PTRACE'
        }
      }

      stages {

        stage('build px4') {
          steps {
            sh('export')
            sh('make distclean')
            sh "ccache -z"
            sh "git fetch --tags"
            sh('make posix_sitl_default')
            sh "ccache -s"
          }
        }

        stage('stash build for coverage') {
          steps {
            stash includes: 'build/**/*', name: 'build_sitl_coverage', useDefaultExcludes: false
          }
          when {
            environment name: 'PX4_CMAKE_BUILD_TYPE', value: 'Coverage'
          }
        }

        stage('unit tests') {
          steps {
            sh 'export'
            sh 'make posix_sitl_default test_results_junit'
            junit 'build/posix_sitl_default/JUnitTestResults.xml'
          }
        }

        stage('build sitl_gazebo') {
          steps {
            sh 'export'
            sh "ccache -z"
            sh('make posix_sitl_default sitl_gazebo')
            sh "ccache -s"
          }
        }

        stage('package') {
          steps {
            sh 'export'
            sh('make posix_sitl_default package')
            stash(name: "px4_sitl_package", includes: "build/posix_sitl_default/*.bz2")
          }
        }

      }

      post {
        always {
            sh 'make distclean'
        }
      }

    }

    stage('ROS Tests') {
      steps {
        script {
          def missions = [
            [
              name: "FW",
              test: "mavros_posix_test_mission.test",
              mission: "FW_mission_1",
              vehicle: "plane"
            ],

            [
              name: "MC_box",
              test: "mavros_posix_test_mission.test",
              mission: "MC_mission_box",
              vehicle: "iris"
            ],
            [
              name: "MC_offboard_att",
              test: "mavros_posix_tests_offboard_attctl.test",
              mission: "",
              vehicle: "iris"
            ],
            [
              name: "MC_offboard_pos",
              test: "mavros_posix_tests_offboard_posctl.test",
              mission: "",
              vehicle: "iris"
            ],

            [
              name: "VTOL_standard",
              test: "mavros_posix_test_mission.test",
              mission: "VTOL_mission_1",
              vehicle: "standard_vtol"
            ],
            [
              name: "VTOL_tailsitter",
              test: "mavros_posix_test_mission.test",
              mission: "VTOL_mission_1",
              vehicle: "tailsitter"
            ],
            [
              name: "VTOL_tiltrotor",
              test: "mavros_posix_test_mission.test",
              mission: "VTOL_mission_1",
              vehicle: "tiltrotor"
            ],

          ]

          def test_nodes = [:]
          for (def i = 0; i < missions.size(); i++) {
            test_nodes.put(missions[i].name, createTestNode(missions[i]))
          }

          parallel test_nodes
        } // script
      } // steps
    } // ROS Tests

    stage('Coverage') {
      parallel {

        stage('code coverage (python)') {
          agent {
            docker {
              image 'px4io/px4-dev-base:2018-09-11'
              args '-e CCACHE_BASEDIR=$WORKSPACE -v ${CCACHE_DIR}:${CCACHE_DIR}:rw --cap-add SYS_PTRACE'
            }
          }
          steps {
            sh 'export'
            sh 'make distclean'
            sh 'make python_coverage'
            withCredentials([string(credentialsId: 'FIRMWARE_CODECOV_TOKEN', variable: 'CODECOV_TOKEN')]) {
              sh 'curl -s https://codecov.io/bash | bash -s - -F python'
            }

            sh 'make distclean'
          }

          when {
            environment name: 'PX4_CMAKE_BUILD_TYPE', value: 'Coverage'
          }
        }

        stage('unit tests') {
          agent {
            docker {
              image 'px4io/px4-dev-base:2018-09-11'
              args '-e CCACHE_BASEDIR=$WORKSPACE -v ${CCACHE_DIR}:${CCACHE_DIR}:rw --cap-add SYS_PTRACE'
            }
          }
          steps {
            sh 'export'
            sh 'make distclean'
            sh 'make posix_sitl_default test_results_junit'
            withCredentials([string(credentialsId: 'FIRMWARE_CODECOV_TOKEN', variable: 'CODECOV_TOKEN')]) {
              sh 'curl -s https://codecov.io/bash | bash -s - -F unittest'
            }
            sh 'make distclean'
          }
          when {
            environment name: 'PX4_CMAKE_BUILD_TYPE', value: 'Coverage'
          }
        }

      } // parallel
    } // stage Coverage

  } //stages
  environment {
    ASAN_OPTIONS = 'detect_stack_use_after_return=1:check_initialization_order=1'
    UBSAN_OPTIONS = 'print_stacktrace=1'
    CCACHE_DIR = '/tmp/ccache'
    CI = true
    CTEST_OUTPUT_ON_FAILURE = 1
  }
  options {
    buildDiscarder(logRotator(numToKeepStr: '10', artifactDaysToKeepStr: '30'))
    timeout(time: 60, unit: 'MINUTES')
  }
} // pipeline

def createTestNode(Map test_def) {
  return {
    node {
      cleanWs()
      docker.image("px4io/px4-dev-ros-kinetic:2018-09-11").inside('-e HOME=${WORKSPACE} --cap-add SYS_PTRACE') {
        stage(test_def.name) {

          try {
            sh('export')

            if (env.PX4_CMAKE_BUILD_TYPE == 'Coverage') {
                checkout(scm)
                unstash 'build_sitl_coverage'
            }

            unstash('px4_sitl_package')
            sh('tar -xjpvf build/posix_sitl_default/px4-posix_sitl_default*.bz2')
            sh('px4-posix_sitl_default*/px4/test/rostest_px4_run.sh ' + test_def.test + ' mission:=' + test_def.mission + ' vehicle:=' + test_def.vehicle)

          } catch (exc) {
            // save all test artifacts for debugging
            archiveArtifacts(allowEmptyArchive: false, artifacts: '.ros/**/*.ulg, .ros/**/rosunit-*.xml, .ros/**/rostest-*.log')
            throw (exc)

          } finally {

            if (env.PX4_CMAKE_BUILD_TYPE == 'Coverage') {
              withCredentials([string(credentialsId: 'FIRMWARE_CODECOV_TOKEN', variable: 'CODECOV_TOKEN')]) {
                sh 'curl -s https://codecov.io/bash | bash -s - -F sitl_mission_${STAGE_NAME}'

                // upload log to flight review (https://logs.px4.io/) with python code coverage
                sh('coverage run -p px4-posix_sitl_default*/px4/Tools/upload_log.py -q --description "${JOB_NAME}: ${STAGE_NAME}" --feedback "${JOB_NAME} ${CHANGE_TITLE} ${CHANGE_URL}" --source CI .ros/log/*/*.ulg')

                // process log data (with python code coverage)
                sh('coverage run -p px4-posix_sitl_default*/px4/Tools/ecl_ekf/process_logdata_ekf.py .ros/log/*/*.ulg')

                // upload python code coverage to codecov.io
                sh 'curl -s https://codecov.io/bash | bash -s - -X gcov -F sitl_python_${STAGE_NAME}'
              }
            }

            // log analysis (non code coverage)
            if (env.PX4_CMAKE_BUILD_TYPE != 'Coverage') {
              // upload log to flight review (https://logs.px4.io/)
              sh('px4-posix_sitl_default*/px4/Tools/upload_log.py -q --description "${JOB_NAME}: ${STAGE_NAME}" --feedback "${JOB_NAME} ${CHANGE_TITLE} ${CHANGE_URL}" --source CI .ros/log/*/*.ulg')

              sh('px4-posix_sitl_default*/px4/Tools/ecl_ekf/process_logdata_ekf.py .ros/log/*/*.ulg')
            }

            // save test artifacts for debugging
            archiveArtifacts(allowEmptyArchive: false, artifacts: '.ros/**/*.pdf, .ros/**/*.csv')
          }

        }
      }
      cleanWs()
    }
  }
}
